/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


#include <log4cxx/logger.h>
#include <log4cxx/net/socketappender.h>
#include <log4cxx/ndc.h>
#include <log4cxx/mdc.h>
#include <log4cxx/asyncappender.h>

#include "socketservertestcase.h"
#include "../util/compare.h"
#include "../util/transformer.h"
#include "../util/controlfilter.h"
#include "../util/absolutedateandtimefilter.h"
#include "../util/threadfilter.h"
#include "../util/filenamefilter.h"
#include <apr_time.h>
#include <log4cxx/file.h>
#include <iostream>
#include <log4cxx/helpers/transcoder.h>
#include <log4cxx/helpers/stringhelper.h>
#include "../testchar.h"
#include "../logunit.h"
#include <log4cxx/spi/loggerrepository.h>

//Define INT64_C for compilers that don't have it
#if (!defined(INT64_C))
	#define INT64_C(value)  value ## LL
#endif

#if defined(WIN32) || defined(_WIN32)
	#include <windows.h>
#endif

using namespace log4cxx;
using namespace log4cxx::helpers;
using namespace log4cxx::net;

#define REGEX_STR(x) x
// %5p %x [%t] %c %m%n
// DEBUG T1 [thread] org.apache.log4j.net.SocketAppenderTestCase Message 1
#define PAT1 \
	REGEX_STR("^(DEBUG| INFO| WARN|ERROR|FATAL|LETHAL) T1 \\[0x[0-9A-F]*]\\ ") \
	REGEX_STR(".* Message [0-9]\\{1,2\\}")

// DEBUG T2 [thread] patternlayouttest.cpp(?) Message 1
#define PAT2 \
	REGEX_STR("^(DEBUG| INFO| WARN|ERROR|FATAL|LETHAL) T2 \\[0x[0-9A-F]*]\\ ") \
	REGEX_STR(".*socketservertestcase.cpp\\([0-9]\\{1,4\\}\\) Message [0-9]\\{1,2\\}")

// DEBUG T3 [thread] patternlayouttest.cpp(?) Message 1
#define PAT3 \
	REGEX_STR("^(DEBUG| INFO| WARN|ERROR|FATAL|LETHAL) T3 \\[0x[0-9A-F]*]\\ ") \
	REGEX_STR(".*socketservertestcase.cpp\\([0-9]\\{1,4\\}\\) Message [0-9]\\{1,2\\}")

// DEBUG some T4 MDC-TEST4 [thread] SocketAppenderTestCase - Message 1
// DEBUG some T4 MDC-TEST4 [thread] SocketAppenderTestCase - Message 1
#define PAT4 \
	REGEX_STR("^(DEBUG| INFO| WARN|ERROR|FATAL|LETHAL) some T4 MDC-TEST4 \\[0x[0-9A-F]*]\\") \
	REGEX_STR(" (root|SocketServerTestCase) - Message [0-9]\\{1,2\\}")
#define PAT5 \
	REGEX_STR("^(DEBUG| INFO| WARN|ERROR|FATAL|LETHAL) some5 T5 MDC-TEST5 \\[0x[0-9A-F]*]\\") \
	REGEX_STR(" (root|SocketServerTestCase) - Message [0-9]\\{1,2\\}")
#define PAT6 \
	REGEX_STR("^(DEBUG| INFO| WARN|ERROR|FATAL|LETHAL) some6 T6 client-test6 MDC-TEST6") \
	REGEX_STR(" \\[0x[0-9A-F]*]\\ (root|SocketServerTestCase) - Message [0-9]\\{1,2\\}")
#define PAT7 \
	REGEX_STR("^(DEBUG| INFO| WARN|ERROR|FATAL|LETHAL) some7 T7 client-test7 MDC-TEST7") \
	REGEX_STR(" \\[0x[0-9A-F]*]\\ (root|SocketServerTestCase) - Message [0-9]\\{1,2\\}")

// DEBUG some8 T8 shortSocketServer MDC-TEST7 [thread] SocketServerTestCase - Message 1
#define PAT8 \
	REGEX_STR("^(DEBUG| INFO| WARN|ERROR|FATAL|LETHAL) some8 T8 shortSocketServer") \
	REGEX_STR(" MDC-TEST8 \\[0x[0-9A-F]*]\\ (root|SocketServerTestCase) - Message [0-9]\\{1,2\\}")



/**
 *  This test checks receipt of SocketAppender messages by the ShortSocketServer
 *  class from log4j.  That class must be started externally to this class
 *  for this test to succeed.
 */
LOGUNIT_CLASS(SocketServerTestCase)
{
	LOGUNIT_TEST_SUITE(SocketServerTestCase);
	LOGUNIT_TEST(test1);
	LOGUNIT_TEST(test2);
	LOGUNIT_TEST(test3);
	LOGUNIT_TEST(test4);
	LOGUNIT_TEST(test5);
	LOGUNIT_TEST(test6);
	LOGUNIT_TEST(test7);
	LOGUNIT_TEST(test8);
	LOGUNIT_TEST_SUITE_END();

	SocketAppenderPtr socketAppender;
	LoggerPtr logger;
	LoggerPtr root;

	class LineNumberFilter : public Filter
	{
		public:
			LineNumberFilter()
			{
				patterns.push_back(PatternReplacement("cpp:[0-9]*", "cpp:XXX"));
			}
	};

public:
	void setUp()
	{
		logger = Logger::getLogger(LOG4CXX_STR("org.apache.log4j.net.SocketServerTestCase"));
		root = Logger::getRootLogger();
	}

	void tearDown()
	{
		socketAppender = 0;
		log4cxx::spi::LoggerRepositoryPtr rep = root->getLoggerRepository().lock();
		if (rep) {
			rep->resetConfiguration();
		}
		logger = 0;
		root = 0;
	}

	/**
	We are testing NDC functionality across the wire.
	*/
	void test1()
	{
		SocketAppenderPtr socketAppender1 = SocketAppenderPtr(
				new SocketAppender(LOG4CXX_STR("localhost"), PORT));
		root->addAppender(socketAppender1);
		common("test1", LOG4CXX_STR("T1"), LOG4CXX_STR("key1"), LOG4CXX_STR("MDC-TEST1"));
		delay(1);

		ControlFilter cf;
		cf << PAT1;

		ThreadFilter threadFilter;

		std::vector<Filter*> filters;
		filters.push_back(&cf);
		filters.push_back(&threadFilter);

		try
		{
			Transformer::transform(TEMP, FILTERED, filters);
		}
		catch (UnexpectedFormatException& e)
		{
			std::cout << "UnexpectedFormatException :" << e.what() << std::endl;
			throw;
		}

		LOGUNIT_ASSERT(Compare::compare(FILTERED, LOG4CXX_FILE("witness/socketServer.1")));
	}

	void test2()
	{
		SocketAppenderPtr socketAppender1 = SocketAppenderPtr(
				new SocketAppender(LOG4CXX_STR("localhost"), PORT));
		root->addAppender(socketAppender1);
		common("test2", LOG4CXX_STR("T2"), LOG4CXX_STR("key2"), LOG4CXX_STR("MDC-TEST2"));
		delay(1);

		ControlFilter cf;
		cf << PAT2;

		ThreadFilter threadFilter;
		LineNumberFilter lineNumberFilter;
		LogString thisFile;
		FilenameFilter filenameFilter(__FILE__, "socketservertestcase.cpp");

		std::vector<Filter*> filters;
		filters.push_back(&filenameFilter);
		filters.push_back(&cf);
		filters.push_back(&threadFilter);
		filters.push_back(&lineNumberFilter);

		try
		{
			Transformer::transform(TEMP, FILTERED, filters);
		}
		catch (UnexpectedFormatException& e)
		{
			std::cout << "UnexpectedFormatException :" << e.what() << std::endl;
			throw;
		}

		LOGUNIT_ASSERT(Compare::compare(FILTERED, LOG4CXX_FILE("witness/socketServer.2")));
	}

	void test3()
	{
		SocketAppenderPtr socketAppender1 = SocketAppenderPtr(
				new SocketAppender(LOG4CXX_STR("localhost"), PORT));
		root->addAppender(socketAppender1);
		common("test3", LOG4CXX_STR("T3"), LOG4CXX_STR("key3"), LOG4CXX_STR("MDC-TEST3"));
		delay(1);

		ControlFilter cf;
		cf << PAT3;

		ThreadFilter threadFilter;
		LineNumberFilter lineNumberFilter;
		LogString thisFile;
		FilenameFilter filenameFilter(__FILE__, "socketservertestcase.cpp");

		std::vector<Filter*> filters;
		filters.push_back(&filenameFilter);
		filters.push_back(&cf);
		filters.push_back(&threadFilter);
		filters.push_back(&lineNumberFilter);

		try
		{
			Transformer::transform(TEMP, FILTERED, filters);
		}
		catch (UnexpectedFormatException& e)
		{
			std::cout << "UnexpectedFormatException :" << e.what() << std::endl;
			throw;
		}

		LOGUNIT_ASSERT(Compare::compare(FILTERED, LOG4CXX_FILE("witness/socketServer.3")));
	}

	void test4()
	{
		SocketAppenderPtr socketAppender1 = SocketAppenderPtr(
				new SocketAppender(LOG4CXX_STR("localhost"), PORT));
		root->addAppender(socketAppender1);
		NDC::push(LOG4CXX_TEST_STR("some"));
		common("test4", LOG4CXX_STR("T4"), LOG4CXX_STR("key4"), LOG4CXX_STR("MDC-TEST4"));
		NDC::pop();
		delay(1);

		ControlFilter cf;
		cf << PAT4;

		ThreadFilter threadFilter;

		std::vector<Filter*> filters;
		filters.push_back(&cf);
		filters.push_back(&threadFilter);

		try
		{
			Transformer::transform(TEMP, FILTERED, filters);
		}
		catch (UnexpectedFormatException& e)
		{
			std::cout << "UnexpectedFormatException :" << e.what() << std::endl;
			throw;
		}

		LOGUNIT_ASSERT(Compare::compare(FILTERED, LOG4CXX_FILE("witness/socketServer.4")));
	}

	void test5()
	{
		SocketAppenderPtr socketAppender1 = SocketAppenderPtr(
				new SocketAppender(LOG4CXX_STR("localhost"), PORT));
		AsyncAppenderPtr asyncAppender = AsyncAppenderPtr(new AsyncAppender());

		root->addAppender(socketAppender1);
		root->addAppender(asyncAppender);

		NDC::push(LOG4CXX_TEST_STR("some5"));
		common("test5", LOG4CXX_STR("T5"), LOG4CXX_STR("key5"), LOG4CXX_STR("MDC-TEST5"));
		NDC::pop();
		delay(2);

		ControlFilter cf;
		cf << PAT5;

		ThreadFilter threadFilter;

		std::vector<Filter*> filters;
		filters.push_back(&cf);
		filters.push_back(&threadFilter);

		try
		{
			Transformer::transform(TEMP, FILTERED, filters);
		}
		catch (UnexpectedFormatException& e)
		{
			std::cout << "UnexpectedFormatException :" << e.what() << std::endl;
			throw;
		}

		LOGUNIT_ASSERT(Compare::compare(FILTERED, LOG4CXX_FILE("witness/socketServer.5")));
	}

	void test6()
	{
		SocketAppenderPtr socketAppender1 = SocketAppenderPtr(
				new SocketAppender(LOG4CXX_STR("localhost"), PORT));
		AsyncAppenderPtr asyncAppender = AsyncAppenderPtr(new AsyncAppender());

		root->addAppender(socketAppender1);
		root->addAppender(asyncAppender);

		NDC::push(LOG4CXX_TEST_STR("some6"));
		MDC::put(LOG4CXX_TEST_STR("hostID"), LOG4CXX_TEST_STR("client-test6"));
		common("test6", LOG4CXX_STR("T6"), LOG4CXX_STR("key6"), LOG4CXX_STR("MDC-TEST6"));
		NDC::pop();
		MDC::remove(LOG4CXX_TEST_STR("hostID"));
		delay(2);

		ControlFilter cf;
		cf << PAT6;

		ThreadFilter threadFilter;

		std::vector<Filter*> filters;
		filters.push_back(&cf);
		filters.push_back(&threadFilter);

		try
		{
			Transformer::transform(TEMP, FILTERED, filters);
		}
		catch (UnexpectedFormatException& e)
		{
			std::cout << "UnexpectedFormatException :" << e.what() << std::endl;
			throw;
		}

		LOGUNIT_ASSERT(Compare::compare(FILTERED, LOG4CXX_FILE("witness/socketServer.6")));
	}

	void test7()
	{
		SocketAppenderPtr socketAppender1 = SocketAppenderPtr(
				new SocketAppender(LOG4CXX_STR("localhost"), PORT));
		AsyncAppenderPtr asyncAppender = AsyncAppenderPtr(new AsyncAppender());

		root->addAppender(socketAppender1);
		root->addAppender(asyncAppender);

		NDC::push(LOG4CXX_TEST_STR("some7"));
		MDC::put(LOG4CXX_TEST_STR("hostID"), LOG4CXX_TEST_STR("client-test7"));
		common("test7", LOG4CXX_STR("T7"), LOG4CXX_STR("key7"), LOG4CXX_STR("MDC-TEST7"));
		NDC::pop();
		MDC::remove(LOG4CXX_TEST_STR("hostID"));
		delay(2);

		ControlFilter cf;
		cf << PAT7;

		ThreadFilter threadFilter;

		std::vector<Filter*> filters;
		filters.push_back(&cf);
		filters.push_back(&threadFilter);

		try
		{
			Transformer::transform(TEMP, FILTERED, filters);
		}
		catch (UnexpectedFormatException& e)
		{
			std::cout << "UnexpectedFormatException :" << e.what() << std::endl;
			throw;
		}

		LOGUNIT_ASSERT(Compare::compare(FILTERED, LOG4CXX_FILE("witness/socketServer.7")));
	}

	void test8()
	{
		SocketAppenderPtr socketAppender1 = SocketAppenderPtr(
				new SocketAppender(LOG4CXX_STR("localhost"), PORT));

		root->addAppender(socketAppender1);

		NDC::push(LOG4CXX_TEST_STR("some8"));
		common("test8", LOG4CXX_STR("T8"), LOG4CXX_STR("key8"), LOG4CXX_STR("MDC-TEST8"));
		NDC::pop();
		delay(2);

		ControlFilter cf;
		cf << PAT8;

		ThreadFilter threadFilter;

		std::vector<Filter*> filters;
		filters.push_back(&cf);
		filters.push_back(&threadFilter);

		try
		{
			Transformer::transform(TEMP, FILTERED, filters);
		}
		catch (UnexpectedFormatException& e)
		{
			std::cout << "UnexpectedFormatException :" << e.what() << std::endl;
			throw;
		}

		LOGUNIT_ASSERT(Compare::compare(FILTERED, LOG4CXX_FILE("witness/socketServer.8")));
	}

	void common(const std::string & testName, const LogString & dc, const LogString & key, const LogString & val)
	{
		int i = -1;
		NDC::push(dc);
		MDC::put(key, val);

		logger->setLevel(Level::getDebug());
		root->setLevel(Level::getDebug());

		LOG4CXX_TRACE(logger, "Message " << i);
		i++;

		logger->setLevel(Level::getTrace());
		root->setLevel(Level::getTrace());

		LOG4CXX_TRACE(logger, "Message " << ++i);
		LOG4CXX_TRACE(root, "Message " << ++i);

		LOG4CXX_DEBUG(logger, "Message " << ++i);
		LOG4CXX_DEBUG(root, "Message " << ++i);

		LOG4CXX_INFO(logger, "Message "  << ++i);
		LOG4CXX_WARN(logger, "Message " << ++i);
		LOG4CXX_FATAL(logger, "Message " << ++i); //5

		std::string exceptionMsg("\njava.lang.Exception: Just testing\n"
			"\tat org.apache.log4j.net.SocketServerTestCase.common(SocketServerTestCase.java:XXX)\n"
			"\tat org.apache.log4j.net.SocketServerTestCase.");
		exceptionMsg.append(testName);
		exceptionMsg.append("(SocketServerTestCase.java:XXX)\n"
			"\tat junit.framework.TestCase.runTest(TestCase.java:XXX)\n"
			"\tat junit.framework.TestCase.runBare(TestCase.java:XXX)\n"
			"\tat junit.framework.TestResult$1.protect(TestResult.java:XXX)\n"
			"\tat junit.framework.TestResult.runProtected(TestResult.java:XXX)\n"
			"\tat junit.framework.TestResult.run(TestResult.java:XXX)\n"
			"\tat junit.framework.TestCase.run(TestCase.java:XXX)\n"
			"\tat junit.framework.TestSuite.runTest(TestSuite.java:XXX)\n"
			"\tat junit.framework.TestSuite.run(TestSuite.java:XXX)");


		LOG4CXX_DEBUG(logger, "Message " << ++i << exceptionMsg);
		LOG4CXX_ERROR(root, "Message " << ++i << exceptionMsg);

		NDC::pop();
		MDC::remove(key);
	}

	void delay(int secs)
	{
		apr_sleep(APR_USEC_PER_SEC * secs);
	}

private:
	static const File TEMP;
	static const File FILTERED;
};

const File SocketServerTestCase::TEMP("output/temp");
const File SocketServerTestCase::FILTERED("output/filtered");

LOGUNIT_TEST_SUITE_REGISTRATION(SocketServerTestCase)
